
/* GCSx
** TILESETEDIT.CPP
**
** Tileset support
** Expands basic TileSet to include editor functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

TileSetEdit::TileSetEdit(WorldEdit* myWorld, int newIsFont, int myId) : TileSet(myWorld, myId) { start_func
    if (myId) assert(myWorld);

    // Find an unused "Untitled" name
    if (myId) {
        string findName(newIsFont ? "font" : "images");
        int pos = 1;
        while (myWorld->findTileSet(findName)) {
            findName = newIsFont ? "font " : "images ";
            findName += intToStr(++pos);
        }
    
        findName[0] = toupper(findName[0]);
        name = findName;
        headerModified = contentModified = 1;
    }
    else headerModified = contentModified = 0;
    
    nameL = name;
    toLower(nameL);

    browserNode = NULL;
    isFont = newIsFont;
    if (newIsFont) {
        defaultTransparent = 1;
        numTilesPerLine = TILE_FONTSET_PERLINE;
    }
    
    disassociated = 0;
    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_CREATE, this, 0, 0);
}

TileSetEdit::~TileSetEdit() { start_func
    // @TODO: doesn't appear to be needed; no way to delete accidentally
    // if (!disassociated) desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_DELETE, this, 0, 0);

    // World should be the one deleting us, which will delete our node also
}

void TileSetEdit::setInfo(WorldEdit* myWorld, int newId) { start_func
    world = myWorld;
    id = newId;
}

void TileSetEdit::disassociate() throw_File { start_func
    // Ensure not cached- our exception point
    cacheLoad();
    
    // Drop browser node
    dropBrowser();
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_DELETE, this, 0, 0);
    
    disassociated = 1;
}

void TileSetEdit::reassociate(TreeView* node) { start_func
    assert(disassociated);

    // Notify objects
    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_CREATE, this, 0, 0);
    
    // Browser node
    addToBrowser(node, 0);

    // Ensure written to file
    headerModified = contentModified = 1;
    disassociated = 0;
}

void TileSetEdit::blitTile(int tileNum, SDL_Surface* surface, int dX, int dY) const { start_func
    assert(surface);
    assert(tileNum >= 0);
    assert(!cached);
    
    if (tileNum <= 0) return;

    // Tile too high?
    if (tileNum > numTiles) {
        drawRect(dX, dY, width, height, guiPacked[COLOR_TEXTBOX], surface);
        SDL_Surface* textSurface = createText(intToStr(tileNum), guiRGB[COLOR_TEXT], 1, FONT_TOOLTIP);
        if (textSurface) {
            blit(0, 0, textSurface, dX, dY, surface, width, height);
        }
        SDL_FreeSurface(textSurface);
        return;
    }

    SDL_SetAlpha(tiles, SDL_SRCALPHA, 255);
    blit(tileNum % tsPerLine * width, tileNum / tsPerLine * height, tiles, dX, dY, surface, width, height);
    SDL_SetAlpha(tiles, 0, 255);
}

void TileSetEdit::blitCollFx(int collNum, SDL_Surface* surface, int dX, int dY, int dim, int mirror, int flip, int rotate90, Uint32 color) const { start_func
    assert(surface);
    assert(collNum >= 0);
    assert(!cached);

    if (collNum <= 0) return;

    // Coll too high? @TODO
    if (collNum > numCollisionMaps) {
        return;
    }
}

void TileSetEdit::blitTileFx(int tileNum, SDL_Surface* surface, int dX, int dY, int alpha, int mirror, int flip, int rotate90, int color) const { start_func
    assert(surface);
    assert(tileNum >= 0);
    assert(!cached);

    if (tileNum <= 0) return;

    // Tile too high? @TODO: show flip/mirror/rotate/alpha/dim, put text in white if needed
    if (tileNum > numTiles) {
        drawRect(dX, dY, width, height, SDL_MapRGB(surface->format, (color & 0xF) << 4, color & 0xF0, (color & 0xF00) >> 4), surface);
        SDL_Surface* textSurface = createText(intToStr(tileNum), guiRGB[COLOR_TEXT], 1, FONT_TOOLTIP);
        if (textSurface) {
            blit(0, 0, textSurface, dX, dY, surface, width, height);
        }
        SDL_FreeSurface(textSurface);
        return;
    }

    alphaBlitFx(tileNum % tsPerLine * width, tileNum / tsPerLine * height, tiles, dX, dY,
                surface, width, height, mirror, flip, rotate90, color & 0xF,
                (color & 0xF0) >> Image::COLOR_BITDEPTH,
                (color & 0xF00) >> (Image::COLOR_BITDEPTH * 2),
                (1 << Image::COLOR_BITDEPTH) - 1, alpha, Image::ALPHA_DEFAULT);
}

void TileSetEdit::loadTile(int tileNum, SDL_Surface* surface) { start_func
    assert(surface);
    assert(tileNum > 0);
    assert(tileNum <= numTiles);
    assert(surface->w >= width);
    assert(surface->h >= height);
    assert(surface->format->BytesPerPixel == 4);
    assert(!cached);

    blit(tileNum % tsPerLine * width, tileNum / tsPerLine * height, tiles, 0, 0, surface, width, height);
}

void TileSetEdit::loadColl(int collNum, SDL_Surface* surface) { start_func
    assert(surface);
    assert(collNum > 0);
    assert(collNum <= numCollisionMaps);
    assert(surface->w >= width);
    assert(surface->h >= height);
    assert(surface->format->BytesPerPixel == 4);
    assert(!cached);

    Uint32* rootSrc = collisionMaps + cmWidth * cmHeight * collNum;
    Uint32* dest = (Uint32*)surface->pixels;
    for (int y = 0; y < height; ++y) {
        Uint32* src = rootSrc + y;
        Uint32 read = *src;
        Uint32 bit = 1;
        for (int x = 0; x < width; ++x) {
            // (overflow to next column)
            if (!bit) {
                bit = 1;
                src += cmHeight;
                read = *src;
            }
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
            *dest = (read & bit) ? 0xffffffff : 0x000000ff;
#else
            *dest = (read & bit) ? 0xffffffff : 0xff000000;
#endif
            bit <<= 1;
            ++dest;
        }
        // Next row
        dest += surface->pitch / 4 - width;
    }
}

void TileSetEdit::saveTile(int tileNum, SDL_Surface* surface, Window* exWin) { start_func
    assert(surface);
    assert(tileNum > 0);
    assert(tileNum <= numTiles);
    assert(surface->w >= width);
    assert(surface->h >= height);
    assert(!cached);

    setContentModified();

    blit(0, 0, surface, tileNum % tsPerLine * width, tileNum / tsPerLine * height, tiles, width, height);

    // Send out update event
    // @TODO: shouldn't send out on import
    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_TILE, this, tileNum, tileNum, exWin);
}

void TileSetEdit::saveColl(int collNum, SDL_Surface* surface, Window* exWin) { start_func
    assert(surface);
    assert(collNum > 0);
    assert(collNum <= numCollisionMaps);
    assert(surface->w >= width);
    assert(surface->h >= height);
    assert(surface->format->BytesPerPixel == 4);
    assert(!cached);

    setContentModified();

    Uint32* rootDest = collisionMaps + cmWidth * cmHeight * collNum;
    Uint32* src = (Uint32*)surface->pixels;
    for (int y = 0; y < height; ++y) {
        Uint32* dest = rootDest + y;
        Uint32 result = 0;
        Uint32 bit = 1;
        for (int x = 0; x < width; ++x) {
            // (overflow to next column)
            if (!bit) {
                bit = 1;
                *dest = result;
                dest += cmHeight;
                result = 0;
            }
            if (*src == 0xffffffff) result |= bit;
            bit <<= 1;
            ++src;
        }
        *dest = result;
        // Next row
        src += surface->pitch / 4 - width;
    }

    // Send out update event
    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_COLL, this, collNum, collNum, exWin);
}

void TileSetEdit::setGlyphWidth(int tileNum, int newWidth, Window* exWin) { start_func
    assert(tileNum > 0);
    assert(tileNum <= numTiles);
    assert(isFont);
    assert(newWidth >= 0);
    assert(newWidth <= width);
    assert(fontWidths);
    
    setContentModified();

    fontWidths[tileNum] = newWidth;
    
    // Send out update event
    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_GLYPHW, this, tileNum, 0, exWin);
}

const Uint8* TileSetEdit::viewTileData(int tile) const { start_func
    assert(tile > 0);
    assert(tile <= numTiles);
    assert(!cached);

    return (Uint8*)tiles->pixels + tile / tsPerLine * height * tiles->pitch + tile % tsPerLine * width * 4;
}

const Uint32* TileSetEdit::viewCollData(int coll) const { start_func
    assert(coll > 0);
    assert(coll <= numCollisionMaps);
    assert(!cached);

    return collisionMaps + coll * cmWidth * cmHeight;
}

int TileSetEdit::viewTilePitch() const { start_func
    assert(!cached);
    assert(tiles);

    return tiles->pitch;
}

Uint8* TileSetEdit::editTileData(int tile) { start_func
    assert(tile > 0);
    assert(tile <= numTiles);
    assert(!cached);
    assert(tiles);
    
    return (Uint8*)tiles->pixels + tile / tsPerLine * height * tiles->pitch + tile % tsPerLine * width * 4;
}

Uint32* TileSetEdit::editCollData(int coll) { start_func
    assert(coll > 0);
    assert(coll <= numCollisionMaps);
    assert(!cached);

    return collisionMaps + coll * cmWidth * cmHeight;
}

void TileSetEdit::editTileDone(int tile, Window* exWin) { start_func
    assert(tile > 0);
    assert(tile <= numTiles);
    assert(!cached);

    setContentModified();

    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_TILE, this, tile, tile, exWin);
}

void TileSetEdit::editCollDone(int coll, Window* exWin) { start_func
    assert(coll > 0);
    assert(coll <= numCollisionMaps);
    assert(!cached);

    setContentModified();

    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_COLL, this, coll, coll, exWin);
}

void TileSetEdit::setHeaderModified() { start_func
    if (!headerModified) headerModified = 1;
}

void TileSetEdit::setContentModified() { start_func
    if (!contentModified) {
        contentModified = 1;
        // No possibility of returning to cache
        delete cacheFile;
        cacheFile = NULL;
    }
}

void TileSetEdit::dropBrowser() { start_func
    browserNode = NULL;
}

void TileSetEdit::addToBrowser(TreeView* node, int makeCurrent) { start_func
    browserNode = new TreeView(name, this, 0, treeviewEventWrap);
    browserNode->setIcon(isFont ? 4 : (numCollisionMaps ? 9 : 5));
    node->insert(browserNode, makeCurrent);
}

int TileSetEdit::treeviewEvent(int code, int command, int check) { start_func
    if (check) {
        if (command == EDIT_DELETE) return Window::COMMAND_ENABLE;
        return Window::COMMAND_HIDE;
    }

    switch (command) {
        case LV_SELECT:
            openBrowseWindow();
            return 1;
            
        case LV_RCLICK:
            // @TODO: Should actually be a popup with this as an item
            propertiesDialog(0, desktop->findPreviousFocusWindow());
            return 1;
        
        case EDIT_DELETE:
        case LV_DELETE:
            // Handles any error conditions/cancellation for us
            assert(world);
            getWorldEdit()->deleteTileset(this, desktop->findPreviousFocusWindow());
            return 1;
    }
    
    return 0;
}

int TileSetEdit::treeviewEventWrap(void* ptr, int code, int command, int check) { start_func
    return ((TileSetEdit*)ptr)->treeviewEvent(code, command, check);
}

void TileSetEdit::setSize(int newWidth, int newHeight, int newCount, int newCollisionCount, Window* srcWin, Window* exWin, SDL_Surface** undo1, Uint32** undo2, Uint32** undo3) throw_Undo { start_func
    // No change?
    if ((newWidth == width) && (newHeight == height) && (newCount == numTiles) && (newCollisionCount == numCollisionMaps)) return;

    // This loads it if cached
    setHeaderModified();
    setContentModified();

    // Undo method?
    if (undo1) {
        assert(undo2);
        swap(*undo1, tiles);
        swap(*undo2, fontWidths);
        swap(*undo3, collisionMaps);

        if (newCount) {
            int sW, sH, newPerLine;
            calculateTileSurfaceSizing(newWidth, newHeight, newCount, sW, sH, newPerLine);
            tsPerLine = newPerLine;
        }
    }

    // (not undo)
    else {
        // (previous NULL surface allowed if no tiles)
        assert(tiles || (numTiles == 0));
        assert(collisionMaps || (numCollisionMaps == 0));
            
        SDL_Surface* newSurface = NULL;
        Uint32* newFontWidths = NULL;
        Uint32* newCollisionMaps = NULL;

        int sW, sH;
        int newPerLine = 0;

        try {
            // No count now = no surface
            if (newCount != 0) {
                // If we've gotten to this function, should already be verified
                assert(newCount == calculateTileSurfaceSizing(newWidth, newHeight, newCount, sW, sH, newPerLine));
                calculateTileSurfaceSizing(newWidth, newHeight, newCount, sW, sH, newPerLine);

                // Allocate new surface (if exception thrown, no harm done to us)
                newSurface = createSurface32(sW, sH);
            
                // Fill with transparent black or solid black
                drawRect(0, 0, sW, sH, mapColor32(0, 0, 0, defaultTransparent ? 0 : 255), newSurface);
                
                // Copy tiles if any
                int toCopy = min(newCount, numTiles) + 1;
                if (toCopy > 1) {
                    int blitHeight = min(newHeight, height);
                    int blitWidth = min(newWidth, width);
                    int x = 0;
                    int y = -newHeight; // Since we immediately increase it at pos 0
                    int oldX = 0;
                    int oldY = -height; // Since we immediately increase it at pos 0
                    for (int pos = 0; pos < toCopy; ++pos) {
                        // Move positions
                        if (pos % tsPerLine) oldX += width;
                        else {
                            oldX = 0;
                            oldY += height;
                        }
                        if (pos % newPerLine) x += newWidth;
                        else {
                            x = 0;
                            y += newHeight;
                        }
            
                        blit(oldX, oldY, tiles, x, y, newSurface, blitWidth, blitHeight);
                    }
                }
    
                // Also allocate new font sizing?
                if (isFont) {
                    newFontWidths = new Uint32[newCount + 1];
                    if (toCopy > 1) {
                        // Ensure glyph widths aren't larger than tile size
                        for (int pos = 0; pos < toCopy; ++pos) {
                            if (fontWidths[pos] > (Uint32)newWidth) newFontWidths[pos] = newWidth;
                            else newFontWidths[pos] = fontWidths[pos];
                        }
                    }
                    if (toCopy < newCount + 1) {
                        memset(newFontWidths + toCopy, 0, (newCount + 1 - toCopy) * sizeof(Uint32));
                        newFontWidths[0] = 0;
                    }
                }
            }
            
            // No count now = no surface
            if (newCollisionCount != 0) {
                sH = newHeight;
                sW = newWidth >> 5;
                if (newWidth & 31) ++sW;
            
                int size = (newCollisionCount + 2) * sH * sW;
                newCollisionMaps = new Uint32[size];
                memset(newCollisionMaps, 0, size * sizeof(Uint32));
                
                int toCopy = min(newCollisionCount, numCollisionMaps) + 1;
                if (toCopy > 1) {
                    // @TODO: Copy over old collision data
                }
            }

            if (world) {
                getWorldEdit()->undo.storeUndoTileSize(id, width, height, numTiles, numCollisionMaps, tiles, fontWidths, collisionMaps, srcWin);
            }
            else {
                if (tiles) SDL_FreeSurface(tiles);
                delete[] fontWidths;
                delete[] collisionMaps;
            }

            tiles = newSurface;
            fontWidths = newFontWidths;
            collisionMaps = newCollisionMaps;
            tsPerLine = newPerLine;
        }
        catch (...) {
            if (newSurface) SDL_FreeSurface(newSurface);
            delete[] newFontWidths;
            delete[] newCollisionMaps;
            throw;
        }
    }
    
    int typeModified = 0;
    if (width != newWidth) typeModified |= OBJMOD_WIDTH;
    if (height != newHeight) typeModified |= OBJMOD_HEIGHT;
    // @TODO: All layers that utilize this set, even those not open, need to be notified of this
    // this notification may cause changes that also need to be undone; either that, or
    // layers get to keep invalid tile references (may be cleaner)
    if (numTiles != newCount) typeModified |= OBJMOD_COUNT;
    if (numCollisionMaps != newCollisionCount) typeModified |= OBJMOD_COUNTCOLL;
    
    width = newWidth;
    height = newHeight;
    calculateCmSizes();
    numTiles = newCount;
    numCollisionMaps = newCollisionCount;

    if (typeModified) desktop->broadcastObjChange(OBJ_TILESET | typeModified, this, 0, 0, exWin);

    // Update node icon
    if (browserNode) browserNode->setIcon(isFont ? 4 : (numCollisionMaps ? 9 : 5));
}

/* (unused)- if used, be sure to integrate into undo/add exWin?
void TileSetEdit::clear(int w, int h, int count) { start_func
    if (tiles) SDL_FreeSurface(tiles);
    delete[] fontWidths;

    setHeaderModified();
    setContentModified();
    
    isFont = 0;
    fontWidths = NULL;

    numTiles = 0;
    tiles = NULL;
    setSize(w, h, count); // THROWS UNDO
    desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_TILE, this, 1, count);
}
*/

void TileSetEdit::setName(const string& newName, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (name != newName) {
        setHeaderModified();
        if (world) {
            getWorldEdit()->undo.storeUndoName(UndoBuffer::UNDO_TILENAME, id, name, newName, srcWin);
            world->deindexTileSet(this);
        }
        name = newName;
        nameL = newName;
        toLower(nameL);
        if (world) world->indexTileSet(this);
        if (browserNode) browserNode->changeName(newName);
    
        desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_NAME, this, 0, 0, exWin);
    }
}

/* (unused)- if used, be sure to integrate into undo; also destroy collision data; also add "exWin" param
void TileSetEdit::setIsFont(int newIsFont) { start_func
    if (((isFont) && (!newIsFont)) || ((!isFont) && (newIsFont))) {
        setHeaderModified();
        
        if ((newIsFont) && (numTiles)) {
            fontWidths = new int[numTiles + 1];
            memset(fontWidths, 0, (numTiles + 1) * sizeof(int));
        }
        else {
            delete[] fontWidths;
            fontWidths = NULL;
        }
        
        isFont = newIsFont;
        
        desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_ISFONT, this, 0, 0);
    }
}
*/

void TileSetEdit::setTilesPerLine(int newTilesPerLine, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (newTilesPerLine != numTilesPerLine) {
        setHeaderModified();
        if (world) getWorldEdit()->undo.storeUndoTilePerLine(id, numTilesPerLine, srcWin);

        numTilesPerLine = newTilesPerLine;

        desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_PERLINE, this, 0, 0, exWin);
    }
}

void TileSetEdit::setDefaultTransparent(int newDefaultTransparent, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (newDefaultTransparent != defaultTransparent) {
        setHeaderModified();
        if (world) getWorldEdit()->undo.storeUndoTileTrans(id, defaultTransparent, srcWin);

        Uint32 changeFrom = mapColor32(0, 0, 0, defaultTransparent ? 0 : 255);
        Uint32 changeTo = mapColor32(0, 0, 0, newDefaultTransparent ? 0 : 255);
        
        defaultTransparent = newDefaultTransparent;

        // All empty tiles should change (tile 0 is technically irrelevant)
        if (numTiles) {
            int firstChanged = -1;
            int lastChanged = -1;
            int x = 0;
            int y = -height; // Since we immediately increase it at pos 0
            int pitch = tiles->pitch / 4;
            for (int pos = 0; pos <= numTiles; ++pos) {
                // Move position
                if (pos % tsPerLine) x += width;
                else {
                    x = 0;
                    y += height;
                }
                
                // Check tile to see if it's entirely the old transparency color
                Uint32* tile = (Uint32*)tiles->pixels + x + y * pitch;
                int found = 0;
                for (int yP = 0; (yP < height) && (!found); ++yP) {
                    for (int xP = 0; xP < width; ++xP) {
                        if (*tile++ != changeFrom) {
                            found = 1;
                            break;
                        }
                    }
                    tile += pitch - width;
                }
                
                // Entirely old color?
                if (!found) {
                    if (firstChanged < 0) firstChanged = pos;
                    lastChanged = pos;
    
                    drawRect(x, y, width, height, changeTo, tiles);
                }
            }
            
            // (don't care about/broadcast tile 0 changing)
            if (lastChanged >= 1) {
                if (firstChanged == 0) firstChanged = 1;
                setContentModified();
                // Don't cascade exWin- this may be an unexpected change
                assert(firstChanged <= lastChanged);
                desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_TILE, this, firstChanged, lastChanged);
            }
        }
        desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_DEFTRANS, this, 0, 0, exWin);
    }
}

void TileSetEdit::openTilePaintWindow(int tile) { start_func
    assert(tile > 0);

    if (!numTiles) return;
    if (tile > numTiles) tile = numTiles;

    try {
        new TilePaint(this, tile);
    }
    catch (FileException& e) {
        guiErrorBox(string(e.details), errorTitleFile);
    }
}

void TileSetEdit::openCollPaintWindow(int coll) { start_func
    assert(coll > 0);

    if (!numCollisionMaps) return;
    if (coll > numCollisionMaps) coll = numCollisionMaps;

    try {
        new TilePaint(this, coll, 1);
    }
    catch (FileException& e) {
        guiErrorBox(string(e.details), errorTitleFile);
    }
}

void TileSetEdit::openBrowseWindow() { start_func
    try {
        new TileSetBrowse(this);
    }
    catch (FileException& e) {
        guiErrorBox(string(e.details), errorTitleFile);
    }
}

int TileSetEdit::propertiesDialog(int newTileSet, Window* srcWin, Window* exWin) { start_func
    int newWidth = width;
    int newHeight = height;
    int newCount = numTiles;
    int newMaps = numCollisionMaps;
    int newPerLine = numTilesPerLine;
    int newDefaultTransparent = defaultTransparent;
    string newName = name;

    if ((newTileSet) && (newCount == 0)) newCount = TILE_DEFAULT_COUNT;
    if ((newTileSet) && (newMaps == 0)) newMaps = COLLISION_DEFAULT_COUNT;
    
    int result;
    if (isFont) result = TileSetPropertiesDialog::createFontSet()->run(&newName, &newWidth, &newHeight, &newCount, &newMaps, &newPerLine, &newDefaultTransparent, this);
    else result = TileSetPropertiesDialog::createTileSet()->run(&newName, &newWidth, &newHeight, &newCount, &newMaps, &newPerLine, &newDefaultTransparent, this);

    if (result) {
        try {
            markLock();
        }
        catch (FileException& e) {
            guiErrorBox(string(e.details), errorTitleFile);
            return 0;
        }
        try {
            if (world) getWorldEdit()->undo.preUndoBlock();
            setName(newName, srcWin, exWin);
            setSize(newWidth, newHeight, newCount, newMaps, srcWin, exWin);
            setTilesPerLine(newPerLine, srcWin, exWin);
            setDefaultTransparent(newDefaultTransparent, srcWin, exWin);
            if (world) getWorldEdit()->undo.postUndoBlock();
        }
        catch (UndoException& e) {
            markUnlock();
            return 0;
        }
        markUnlock();
        return 1;
    }
    
    return 0;
}

int TileSetEdit::calculateTileSurfaceSizing(int w, int h, int count, int& sWidth, int& sHeight, int& perLine) { start_func
    // One extra blank tile
    ++count;
    
    // Determine how many columns minimum we require
    int maxRows = TILE_SURFACE_MAX / h;
    perLine = count / maxRows;
    if (count % maxRows) ++perLine;

    // Determine width
    sWidth = perLine * w;

    // Too large?
    if (sWidth > TILE_SURFACE_MAX) {
        // Limit size
        perLine = TILE_SURFACE_MAX / w;
        
        // Adjust count
        assert(perLine * maxRows < count);
        count = perLine * maxRows;
        
        // New width
        sWidth = perLine * w;
    }
    
    // Determine how many rows we want, aiming to balance the columns evenly
    int numRows = count / perLine;
    if (count % perLine) ++numRows;
    assert(numRows <= maxRows);
    
    // Determine height
    sHeight = numRows * h;
    assert(sHeight <= TILE_SURFACE_MAX);

    return count - 1;
}

int TileSetEdit::isHeaderModified() const { start_func
    return headerModified;
}

int TileSetEdit::isContentModified() const { start_func
    return contentModified;
}

Uint32 TileSetEdit::saveHeader(FileWrite* file) throw_File { start_func
    assert(world);
    assert(id);
    
    file->writeStr(name);
    file->writeInt(width);
    file->writeInt(height);
    file->writeInt(numTiles);
    file->writeInt(numCollisionMaps);
    file->writeInt(numTilesPerLine);
    file->writeInt(tsPerLine);
    file->writeInt(defaultTransparent);
    file->writeInt(isFont);
    file->writeInt(id);
    
    return 1;
}

void TileSetEdit::saveContent(FileWrite* file) throw_File { start_func
    assert((tiles) || (numTiles == 0));
    assert(world);
    assert(id);

    if (numTiles) {
        file->writeInt(tiles->w);
        file->writeInt(tiles->h);
        file->writeInt(tiles->w * tiles->h * 4);

        // If pitch of created surface is "incorrect", we need to do extra work
        if (tiles->pitch == tiles->w * 4) {
            file->write(tiles->pixels, tiles->w * tiles->h * 4);
        }
        else {
            Uint8* src = (Uint8*)tiles->pixels;
            for (int row = tiles->h; row > 0; --row) {
                file->write(src, tiles->w * 4);
                src += tiles->pitch;
            }
        }

        if (isFont) file->writeIntBulk(fontWidths + 1, numTiles);
    }

    if (numCollisionMaps) {
        file->writeIntBulk(collisionMaps + cmWidth * cmHeight,
                           (numCollisionMaps + 1) * cmWidth * cmHeight);
    }
}

void TileSetEdit::saveSuccess() { start_func
    headerModified = 0;
    contentModified = 0;
}

void TileSetEdit::cachedContent(FileRead* file, int oldData) { start_func
    // If already cached..
    if (cached) {
        delete cacheFile;
        cacheFile = file;
    }
    else if (oldData) {
        delete file;
    }
    else {
        // Cache ourselves?
        if (!lockCount) {
            delete cacheFile;
            cacheFile = file;
            if (tiles) SDL_FreeSurface(tiles);
            tiles = NULL;
            delete[] fontWidths;
            fontWidths = NULL;
            delete[] collisionMaps;
            collisionMaps = NULL;
            cached = 1;
        }
        else {
            delete file;
        }
    }
}

